package dao.hibernate;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;

import entities.Sheet;
import entities.ValueAndType;
import service.configuration.IConfiguration;
import service.configuration.NamingUtil;




public abstract class DaoHibernate {

	protected HibernateUtil hibernateUtil;
	protected SheetFactory sheetFactory;
	protected IConfiguration config;
	
	
	public DaoHibernate(HibernateUtil hibernateUtil, SheetFactory sheetFactory,
			IConfiguration config) {
		
		this.hibernateUtil = hibernateUtil;
		this.sheetFactory = sheetFactory;
		this.config = config;
	}
	
	
	
    /**
     * Ajoute une fiche dans la base de données en utilisant la Map
     * 'properties', qui doit contenir une propriété 'code' (la clef primaire)
     * et une propriété '$type$' (le type de fiche utilisé pour Hibernate pour
     * retrouver la bonne table).
     * 
     * @param sheet
     *            un objet fiche contenant au moins un code et un type
     */
    public void addSheet(Sheet sheet) throws HibernateException {
            if (sheet == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    session.save(sheet.getType(), sheet.getProperties());
                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    /**
     * Cherche une fiche dans la base à partir d'un code et d'un type de
     * fiche.
     * 
     * @param code
     *            le code de la fiche cherchée
     * @param type
     *            le type de la fiche cherchée
     * @return l'objet fiche trouvé ou null si pas de résultat
     */
    public Sheet findSheetByCode(String code, String type)
                    throws HibernateException {
            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    Sheet sheet = null;

                    @SuppressWarnings("unchecked")
                    Map<String, Object> properties = (Map<String, Object>) session.get(
                                    type, code);

                    if (properties == null) {
                            return null;
                    } else {
                            sheet = sheetFactory.createSheet(properties);
                            // sheet = new Sheet();
                            // sheet.setProperties(properties);
                            return sheet;
                    }
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }
    

    /**
     * Met à jour la fiche en remplaçant toutes les valeurs de toutes les
     * propriétés présentes dans la base par les valeurs de l'objet fiche
     * passé en argument.
     * 
     * @param sheet	la fiche à mettre à jour
     */
    public void updateSheet(Sheet sheet) throws HibernateException {
            if (sheet == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    session.update(sheet.getType(), sheet.getProperties());
                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    /**
     * Supprime une fiche de la base.
     * 
     * @param sheet	la fiche à supprimer
     */
    public void removeSheet(Sheet sheet) throws HibernateException {
            if (sheet == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    session.delete(sheet.getType(), sheet.getProperties());
                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    /**
     * Ajoute une liste de fiches dans la base.
     * 
     * @param sheets	une liste de fiches à ajouter
     */
    public void addSheets(List<Sheet> sheets) {
            if (sheets == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;
            int i = 0;

            try {
                    transaction = session.beginTransaction();

                    for (Sheet sheet : sheets) {
                            session.save(sheet.getType(), sheet.getProperties());

                            // batch insert :
                            if (++i % hibernateUtil.getJdbcBatchSize() == 0) {
                                    session.flush();
                                    session.clear();
                            }
                    }

                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    
    /**
     * Cherche toutes les fiches du type passé en argument dans la base.
     * 
     * @param type	le type de fiches à chercher
     * @return	la liste des fiches trouvées (vide si pas de résultat)
     */
    public List<Sheet> findAllSheets(String type) {
            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    Criteria query = session.createCriteria(type);

                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> results = query.list();

                    List<Sheet> sheets = new ArrayList<Sheet>();
                    for (Map<String, Object> properties : results) {
                            Sheet sheet = sheetFactory.createSheet(properties);
                            // Sheet sheet = new Sheet();
                            // sheet.setProperties(properties);
                            sheets.add(sheet);
                    }

                    return sheets;
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    /**
     * Cherche toutes fiches dans la base dont la propriété a l'une des
     * valeur de la liste de valeurs passée en argument.
     * 
     * @param propertyName	la clef de la propriété utilisée pour la recherche
     * @param propertyValue	une liste de valeurs à chercher pour cette propriété
     * @return la liste des fiches trouvées (vide si pas de résultat)
     */
    public List<Sheet> findSheetsByProperty(String propertyName,
                    List<String> values, String type) {

            if (values == null || values.isEmpty() || type == null) {
                    return new ArrayList<Sheet>();
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    Criteria query = session.createCriteria(type);

                    Disjunction disjonction = Restrictions.disjunction();
                    for (String propertyValue : values) {
                            disjonction.add(Restrictions.eq(propertyName, propertyValue));
                    }

                    query.add(disjonction);

                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> results = query.list();

                    List<Sheet> sheets = new ArrayList<Sheet>();
                    for (Map<String, Object> properties : results) {
                            Sheet sheet = sheetFactory.createSheet(properties);
                            // Sheet sheet = new Sheet();
                            // sheet.setProperties(properties);
                            sheets.add(sheet);
                    }

                    return sheets;
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    /**
     * Cherche une fiche dans la base avec la valeur d'une propriété.
     * 
     * @param propertyName	la clef de la propriété utilisée pour la recherche
     * @param propertyValue	la valeur à chercher pour cette propriété
     * @return une liste de fiches trouvées (vide si pas de résultat)
     */
    public List<Sheet> findSheetsByProperty(String propertyName,
                    String propertyValue, String type) throws HibernateException {

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    Criteria query = session.createCriteria(type);

                    query.add(Restrictions.eq(propertyName, propertyValue));

                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> results = query.list();

                    List<Sheet> sheets = new ArrayList<Sheet>();
                    for (Map<String, Object> properties : results) {
                            Sheet sheet = sheetFactory.createSheet(properties);
                            // Sheet sheet = new Sheet();
                            // sheet.setProperties(properties);
                            sheets.add(sheet);
                    }

                    return sheets;
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    
    /**
     * Cherche toutes les fiches avec une des valeurs passées en argument pour la propriété.
     * 
     * @param propertyName	la clef de la propriété utilisée pour la recherche
     * @param propertyValue	la valeur à chercher pour cette propriété
     * @return une liste de fiches trouvées (vide si pas de résultat)
     */
    public List<Sheet> findSheetsByPropertyLike(String propertyName,
                    String propertyValue, String type) throws HibernateException {

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    Criteria query = session.createCriteria(type);

                    query.add(Restrictions.like(propertyName, propertyValue,
                                    MatchMode.START));

                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> results = query.list();

                    List<Sheet> sheets = new ArrayList<Sheet>();
                    for (Map<String, Object> properties : results) {
                            Sheet sheet = sheetFactory.createSheet(properties);
                            sheets.add(sheet);
                    }

                    return sheets;
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    

    /**
     * Cherche toutes les fiches avec une des valeurs passées en argument pour la propriété.
     * Avec cette méthode 
     * 
     * @param propertyName	la clef de la propriété utilisée pour la recherche
     * @param propertyValue	la valeur à chercher pour cette propriété
     * @return une liste de fiches trouvées (vide si pas de résultat)
     */
    public List<Sheet> findSheetsByProperty(String propertyName,
                    List<ValueAndType> valuesAndTypes) {

            // on regroupe les valeurs par type de sheet :
            Map<String, List<String>> typeAndValues = new HashMap<String, List<String>>();
            for (ValueAndType valueAndType : valuesAndTypes) {
                    List<String> values = null;

                    if (typeAndValues.containsKey(valueAndType.getType())) {
                            values = typeAndValues.get(valueAndType.getType());
                    } else {
                            values = new ArrayList<String>();
                            typeAndValues.put(valueAndType.getType(), values);
                    }

                    values.add(valueAndType.getValue());
            }

            // / on recherche la liste de valeurs pour la propriété pour chaque type
            // de sheet
            List<Sheet> sheets = new ArrayList<Sheet>();
            for (Entry<String, List<String>> entry : typeAndValues.entrySet()) {
                    sheets.addAll(findSheetsByProperty(NamingUtil.CODE,
                                    entry.getValue(), entry.getKey()));
            }

            return sheets;
    }

    
    
    /**
     * Supprime toutes les fiches de la liste passée en argument.
     * 
     * @param sheets	une liste de fiches à supprimer
     */
    public void removeSheets(List<Sheet> sheets) {
            if (sheets == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;
            int i = 0;

            try {
                    transaction = session.beginTransaction();

                    for (Sheet sheet : sheets) {
                            session.delete(sheet.getType(), sheet.getProperties());

                            // batch insert :
                            if (++i % hibernateUtil.getJdbcBatchSize() == 0) {
                                    session.flush();
                                    session.clear();
                            }
                    }

                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    
    /**
     * Supprime toutes les fiches du type passé en argument.
     * 
     * @param type	le type des fiches à supprimer
     */
    public void removeAllSheets(String type) {
            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;

            try {
                    transaction = session.beginTransaction();
                    String query = "delete from "
                                    + config.getSheetConfig(type).getTable();

                    session.createSQLQuery(query).executeUpdate();

                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }

    
    
    
    /**
     * Met à jour toutes les fiches passées en argument.
     * 
     * @param type	le type des fiches à supprimer
     */
    public void updateSheets(List<Sheet> sheets) {
            if (sheets == null) {
                    return;
            }

            Session session = hibernateUtil.getSessionFactory().openSession();
            Transaction transaction = null;
            int i = 0;

            try {
                    transaction = session.beginTransaction();

                    for (Sheet sheet : sheets) {
                            session.update(sheet.getType(), sheet.getProperties());

                            if (++i % hibernateUtil.getJdbcBatchSize() == 0) {
                                    session.flush();
                                    session.clear();
                            }
                    }

                    transaction.commit();
            } catch (HibernateException e) {
                    transaction.rollback();
                    throw e;
            } finally {
                    session.close();
            }
    }



}
